package com.futuristik.arkitek;

import java.util.List;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.BodyDef.BodyType;
import com.badlogic.gdx.physics.box2d.CircleShape;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.physics.box2d.WorldManifold;
import com.futuristik.objects.ArkitekObject;
import com.futuristik.objects.Flag;

public class Character {
	public static final int IDLE = 0;
	public static final int RUN = 1;
	public static final int JUMP = 2;
	public static final int SPAWN = 3;
	public static final int DYING = 4;
	public static final int DEAD = 5;
	static final boolean LEFT = false;
	static final boolean RIGHT = true;
		
	static final float ACCELERATION = 20f;
	static final float JUMP_VELOCITY = 10;
	static final float GRAVITY = 20.0f;
	static final float MAX_VELOCITY = 6f;
	static final float DAMP = 0.90f;
	
	public int rightDown = -1;
	public int leftDown = -1;
	public int jump = -1;
	
	boolean dir = RIGHT;
	
	Body playerBody;
	Fixture playerPhysicsFixture;
	Fixture playerSensorFixture;
        
    Animation                       walkRightAnimation;          
    Animation                       walkLeftAnimation;
    Animation						deathAnimation;
    Animation						upAnimation;
    Animation						upToDownAnimation;
    Animation						downAnimation;
    Animation						idleAnimation;
    
    Texture							animationSheet;
    
    Texture                         walkSheet;              
    TextureRegion[]                 walkFrames;             
    
    TextureRegion[]                 walkRightFrames;        
    TextureRegion[]                 walkLeftFrames;        
    
    TextureRegion[]					idleFrames;
    TextureRegion					idleFrame;
    
    TextureRegion[]					upFrames;
    TextureRegion[]					upToDownFrames;
    TextureRegion[]					downFrames;
    TextureRegion[]					deathFrames;
    
    SpriteBatch                     spriteBatch;            
    TextureRegion                   currentFrame;           
    	
	public TextureRegion characterSprite;
	
	boolean grounded = false;
	
	float stillTime = 0;
	long lastGroundTime = 0;

	public Flag lastCheckpoint;
	public int state = SPAWN;
	public float stateTime = 0;

    int jumpFlag = 0;
	
	World world;
	
	public Character (World w) {		
		world = w;
		
		//Tweak this number to make the animation play faster or slower (higher is slower, lower is faster)
		float animationTime = .06f;
		
		animationSheet = new Texture(Gdx.files.internal("data/dude_animation.png"));
		deathFrames = new TextureRegion[6];
		deathFrames[0] = new TextureRegion(animationSheet, 0, 1024-132, 100, 132);
		deathFrames[1] = new TextureRegion(animationSheet, 100, 1024-132, 100, 132);
		deathFrames[2] = new TextureRegion(animationSheet, 194, 1024-132, 94, 132);
		deathFrames[3] = new TextureRegion(animationSheet, 289, 1024-132, 96, 132);
		deathFrames[4] = new TextureRegion(animationSheet, 390, 1024-132, 96, 132);
		deathFrames[5] = new TextureRegion(animationSheet, 487, 1024-132, 114, 132);
		
		deathAnimation = new Animation(.3f, deathFrames);
		
		upToDownFrames = new TextureRegion[9];
		upToDownFrames[0] = new TextureRegion(animationSheet, 0, 745, 110, 137);
		upToDownFrames[1] = new TextureRegion(animationSheet, 110, 745, 110, 137);
		upToDownFrames[2] = new TextureRegion(animationSheet, 220, 745, 110, 137);
		upToDownFrames[3] = new TextureRegion(animationSheet, 330, 745, 110, 137);
		upToDownFrames[4] = new TextureRegion(animationSheet, 440, 745, 110, 137);
		upToDownFrames[5] = new TextureRegion(animationSheet, 550, 745, 110, 137);
		upToDownFrames[6] = new TextureRegion(animationSheet, 660, 745, 110, 137);
		upToDownFrames[7] = new TextureRegion(animationSheet, 770, 745, 110, 137);
		upToDownFrames[8] = new TextureRegion(animationSheet, 880, 745, 114, 137);
		
		upToDownAnimation = new Animation(.2f, upToDownFrames);
		
		upFrames = new TextureRegion[3];
		upFrames[0] = new TextureRegion(animationSheet, 0, 570, 110, 137);
		upFrames[1] = new TextureRegion(animationSheet, 110, 570, 110, 137);
		upFrames[2] = new TextureRegion(animationSheet, 220, 570, 110, 137);
		
		upAnimation = new Animation(.25f, upFrames);
		
		downFrames = new TextureRegion[3];
		downFrames[0] = new TextureRegion(animationSheet, 439, 567, 114, 137);
		downFrames[1] = new TextureRegion(animationSheet, 561, 567, 114, 137);
		downFrames[2] = new TextureRegion(animationSheet, 682, 567, 114, 137);
		
		downAnimation = new Animation(.25f, downFrames);
		
		
		/*
		idleFrames = new TextureRegion[22];
		
		int index = 0;
		for(int i = 0; i < 3; i++){
			for(int j = 0; j < 8; j++){
				if(index < 22){
					System.out.println(index+" "+116*j+" "+170*i);
					idleFrames[index++] = new TextureRegion(animationSheet, 116*j, 170*i, 116, 170);
				}
			}
		}
		
		idleAnimation = new Animation(.06f, idleFrames);
		*/
		
		walkSheet = new Texture(Gdx.files.internal("data/dude_animation_sheet.png"));     // This loads teh frames for the Character walk Animation
		
		walkLeftFrames = new TextureRegion[27]; //This allocates space for the 27 frame animation of teh walking motion in the LEFT direction
		walkRightFrames = new TextureRegion[27];
		
		/*
		 * The animation is created by splitting the big image into the 27 pieces with each piece being a frame of
		 * the animation.
		 * 
		 * The TextureRegion function grabs a section of an image (in this case, dude_animation_sheet.png)
		 * The parameters are:
		 * 		- X coordinate in the image 
		 * 		- Y coordinate in the image
		 * 		- Width of the section
		 * 		- Height of the section
		 * 
		 * NOTE: The X and Y coordinates are relative to the top left corner of the image file ( top left = (0, 0) )
		 * 
		 * 
		 * There are two lists of frames for left and right walking animations. In this case, they are the exact same images except
		 * for the left animation, they are flipped.
		 * 
		 * The method of construction of the lists of frames is arbitrary as long as they are in order and can be of any length.
		 * Obviously the higher the number of frames, the better the animation looks.
		 */
		
		int index = 0;
		for(int i = 0; i < 4; i++){
			for(int j = 0; j < 7; j++){
				if(index < 27){
					TextureRegion tempFrame = new TextureRegion(walkSheet, 130*j, 150*i, 130, 150);
					tempFrame.flip(true, false); //Here the frame is flipped before it is added to the walking left frames
					walkLeftFrames[index] = tempFrame;
					
					walkRightFrames[index++] = new TextureRegion(walkSheet, 130*j, 150*i, 130, 150);
					
				}
				else //This is here because the last frame in the image is the idle image
					idleFrame = new TextureRegion(walkSheet, 130*j, 150*i, 130, 150);
			}
		}
		
		walkRightAnimation = new Animation(animationTime, walkRightFrames);
		walkLeftAnimation = new Animation(animationTime, walkLeftFrames);
		stateTime = 0f;                                                 
		
		/*
		 * The section below creates the player body and adds it to the World object.
		 * 
		 * This is how the character is represented in the physics World.
		 */
		
		BodyDef def = new BodyDef(); //initialize Body definition to begin the creation
		def.type = BodyType.DynamicBody; //Set that it is a DynamicBody meaning that it can move freely
		Body box = w.createBody(def); //creates a body from the definition and adds it to the World's list of bodies
 
		/*
		 * This portion generates the rectangle part of the character that is the top of the character model.
		 */
		PolygonShape poly = new PolygonShape();		
		poly.setAsBox(.6f, .6f); //creates the polygon as a box of given width and height
		FixtureDef topDef = new FixtureDef();
			topDef.friction = 0;
			topDef.shape = poly;
		playerPhysicsFixture = box.createFixture(topDef); //this attaches the "fixture" to the body within the World and gives it a density
		poly.dispose(); //removes the temporarily generated shape
 
		CircleShape circle = new CircleShape();		
		circle.setRadius(0.61f);
		circle.setPosition(new Vector2(0, -.6f));
		FixtureDef bottomDef = new FixtureDef();
			bottomDef.density = 2f;
			bottomDef.friction = 5f;
			bottomDef.shape = circle;
		playerSensorFixture = box.createFixture(bottomDef);		
		circle.dispose();		
 		
		box.setBullet(true);
		state = SPAWN;
		stateTime = 0;
		playerBody = box;
		playerBody.setFixedRotation(true);
		playerSensorFixture.setFriction(0.2f);
	}
	
	public void render (SpriteBatch batch){
		Vector2 pos = playerBody.getPosition(); //this provides a Vector2 object containing the player's position in the physics World.
		Vector2 vel = playerBody.getLinearVelocity();
		
        stateTime += Gdx.graphics.getDeltaTime();   //increments the stateTime value by the amount of time it has been since the last render call
        
        if(ArkitekObject.round(Math.abs(vel.y)) > 0 && !grounded){
        	float r = ArkitekObject.round(vel.x);
	        if(r > 0){
	        	dir = RIGHT;
	        }
	        else if(r < 0){
	        	dir = LEFT;
	        }
        }
        
        float xPos = pos.x - 1;
        float yPos = pos.y - 1.3f;
        float width = 2;
        float height = 2;
        
        if(!grounded && vel.y > 1 && state != DYING){
        	jumpFlag = 0;
		}
        else if(jumpFlag == 0 && vel.y < 1 && state != DYING){
        	jumpFlag = 1;
        	stateTime = 0f;
        }
        
        if(isCharacterGrounded(Gdx.graphics.getDeltaTime())){
        	jumpFlag = -1;
        }
        
        System.out.println(jumpFlag);
        
        /*
         * This section chooses the frame to be drawn based on the STATE of the character.
         */
        if(state == RUN && grounded){
        	//If the character is moving left or right, we choose the walking animation and set a conditional to choose the direction.
        	currentFrame = walkRightAnimation.getKeyFrame(stateTime, true);  
        }
        else if(state == DYING){
        	playerBody.setLinearVelocity(new Vector2());
        	
        	currentFrame = deathAnimation.getKeyFrame(stateTime, false);
        	yPos -= 5*stateTime;
        	width = 1.6f;
        	if(currentFrame.equals(deathFrames[5]))
        			state = DEAD;
        }
        else if(state == DEAD){
        	state = SPAWN;
        }
        else if(!grounded && jumpFlag == 0){
        	yPos = pos.y - 1.1f;
        	xPos = pos.x - .9f;
        	width = 1.7f;
        	height = 1.8f;
        	currentFrame = upAnimation.getKeyFrame(stateTime, true);
        }
        else if(vel.y < 1 && !grounded && (jumpFlag == 0 || jumpFlag == 1)){
        	if(jumpFlag == 0){
        		stateTime = 0f;
        		jumpFlag = 1;
        	}
        	yPos = pos.y - 1.1f;
        	xPos = pos.x - .9f;
        	width = 1.7f;
        	height = 1.8f;
        	currentFrame = upToDownAnimation.getKeyFrame(stateTime, false);
        	
        	if(currentFrame.equals(upToDownFrames[8])){
        		jumpFlag = 2;
        		stateTime = 0f;
        	}
        }
        else if(!grounded && jumpFlag == 2){
        	yPos = pos.y - 1.1f;
        	xPos = pos.x - .9f;
        	width = 1.7f;
        	height = 1.8f;
        	currentFrame = downAnimation.getKeyFrame(stateTime, true);
        }
        else //If nothing is happening or something is happening that doesn't require it's own specific animation, we default to an idle state texture.
        	currentFrame = idleFrame;
        
        TextureRegion dirFrame = new TextureRegion(currentFrame);
        if(dir == LEFT)
        	dirFrame.flip(true, false);
        
        //This command draws the Character!!!!!
        batch.draw(dirFrame, xPos, yPos, 0, 0, width, height, 1, 1, 0);       
        
        //System.out.println("Current Player Position: ("+pos.x+", "+pos.y+")");	
	}
	
	public void update (float deltaTime) {
		if(state != DYING && state != DEAD) 
			processKeys();

		if (state == SPAWN) {
			if (stateTime > 0.4f) {
				state = IDLE;
			}
		}

		stateTime += deltaTime;
	}
	
	private void processKeys () {		
		//Controls go here !!!
		Vector2 vel = playerBody.getLinearVelocity();
		Vector2 pos = playerBody.getPosition();		
		grounded = isCharacterGrounded(Gdx.graphics.getDeltaTime());
		
		if(grounded) {
			lastGroundTime = System.nanoTime();
		} else {
			if(System.nanoTime() - lastGroundTime < 100000000) {
				grounded = true;
			}
		}
 
		/*
		// cap max velocity on x		
		if(state != JUMP && Math.abs(vel.x) > MAX_VELOCITY) {			
			vel.x = Math.signum(vel.x) * MAX_VELOCITY;
			playerBody.setLinearVelocity(vel.x, vel.y);
		}
		*/

		/*		Jump Controller 	 */
		
		if(Gdx.input.isKeyPressed(Keys.W) || (state != DYING && jump > -1)){
			if(grounded){
				state = JUMP;
				jumpFlag = 0;
				grounded = false;
				playerBody.setLinearVelocity(vel.x, 0);	
				playerBody.setTransform(pos.x, pos.y + 0.01f, 0);
				playerBody.applyLinearImpulse(0, 30, pos.x, pos.y);
			}
		}	
			
		// apply left impulse, but only if max velocity is not reached yet
		if(leftDown != jump || Gdx.input.isKeyPressed(Keys.A)) {
			if(grounded)
				state = RUN;
			dir = LEFT;
			
			if(vel.x > -MAX_VELOCITY)
				playerBody.applyLinearImpulse(-2f, 0, pos.x, pos.y);
		}
 
		// apply right impulse, but only if max velocity is not reached yet
		else if(rightDown != jump || Gdx.input.isKeyPressed(Keys.D)) {
			if(grounded)
				state = RUN;
			dir = RIGHT;
			
			if(vel.x < MAX_VELOCITY)
				playerBody.applyLinearImpulse(2f, 0, pos.x, pos.y);
		}
		else
			state = IDLE;
	}
	
	public void spawnAtFlag(Flag flag){
		this.spawnAtPoint(flag.getX(), flag.getY());
	}
	
	public void spawnAtPoint(float x, float y){
		playerBody.setTransform(x+1, y+1.32f, 0);
		playerBody.setLinearVelocity(0, 0);
	}
	
	private boolean isCharacterGrounded(float deltaTime) {	
		List<Contact> contactList = this.world.getContactList();
		for(int i = 0; i < contactList.size(); i++) {
			Contact contact = contactList.get(i);
			if(contact.isEnabled() && contact.isTouching() && (contact.getFixtureB() == playerSensorFixture)) {				
				
				WorldManifold manifold = contact.getWorldManifold();
				Vector2 norm = manifold.getNormal();
				float rad = MathUtils.atan2(norm.y, norm.x) - MathUtils.PI/2f;
				
				if(Math.abs(rad) <= MathUtils.PI/4f || (Math.abs(rad) <= MathUtils.PI/2f && playerBody.getLinearVelocity().y == 0)){
					return true;
				}
				/*
				boolean below = true;
				for(int j = 0; j < manifold.getNumberOfContactPoints(); j++) {
					below &= (manifold.getPoints()[j].y < pos.y - .6f);
				}
 
				if(below) {
					if(contact.getFixtureA().getUserData() != null && contact.getFixtureA().getUserData().equals("p")) {
						groundedPlatform = (MovingPlatform)contact.getFixtureA().getBody().getUserData();							
					}
 
					if(contact.getFixtureB().getUserData() != null && contact.getFixtureB().getUserData().equals("p")) {
						groundedPlatform = (MovingPlatform)contact.getFixtureB().getBody().getUserData();
					}											
					return true;			
				}
 
				return false;
				*/
			}
		}
		return false;
	}
	
	//GETTERS
	public Body getBody(){
		return playerBody;
	}
	
	public boolean isGrounded(){
		return grounded;
	}
}
